; docformat = 'rst'
;
; NAME:
;       CDF_PLOT__DEFINE
;
;*****************************************************************************************
;   Copyright (c) 2013, Matthew Argall                                                   ;
;   All rights reserved.                                                                 ;
;                                                                                        ;
;   Redistribution and use in source and binary forms, with or without modification,     ;
;   are permitted provided that the following conditions are met:                        ;
;                                                                                        ;
;       * Redistributions of source code must retain the above copyright notice,         ;
;         this list of conditions and the following disclaimer.                          ;
;       * Redistributions in binary form must reproduce the above copyright notice,      ;
;         this list of conditions and the following disclaimer in the documentation      ;
;         and/or other materials provided with the distribution.                         ;
;       * Neither the name of the <ORGANIZATION> nor the names of its contributors may   ;
;         be used to endorse or promote products derived from this software without      ;
;         specific prior written permission.                                             ;
;                                                                                        ;
;   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY  ;
;   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ;
;   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT  ;
;   SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       ;
;   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ;
;   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR   ;
;   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN     ;
;   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN   ;
;   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  ;
;   DAMAGE.                                                                              ;
;*****************************************************************************************
;
; PURPOSE:
;+
;       A class for plotting variable data from CDF files.
;
; :Author:
;       Matthew Argall::
;		University of New Hampshire
;		Morse Hall, Room 113
;       8 College Rd.
;		Durham, NH, 03824
;       matthew.argall@wildcats.unh.edu
;
; :Categories:
;       CDF Utilities, File I/O, Data Reader
;
; :History:
;   Modification History::
;       08/08/2012  -   Written by Matthew Argall
;       09/08/2013  -   SCALETYP is now handled properly. Legends are made for time
;                           series data. Added the REPLACE_FILLVAL keyword to Plot. - MRA
;       09/08/2013  -   Some LABL_PTR_1 variables are not reported correctly. Now check if
;                           the label variable exists before reading its data. - MRA
;       09/15/2013  -   Organize_Dimensions accounts for dependent variables with a record
;                           variance of "VARY". Dimension ordering is working for 3D
;                           spectrograms. Also, the dependent variable is reversed, if
;                           found to be monotonically decreasing. - MRA
;       09/22/2013  -   Use EpochTickFormat.pro to label the x-axes. - MRA
;       09/23/2013  -   If MIN_VALUE and MAX_VALUE are arrays, the first element of each
;                           is selected. - MRA
;       09/25/2013  -   Back to converting times to seconds since midnight and using
;                           time_labels for a tick format. Convert_Coord() returns floats
;                           and the values in EpochTickFormat are doubles, neither of
;                           which have enough precision to keep track of TT_2000 times
;                           accurately. - MRA
;       2013/11/15  -   Added the DELTA_PLUS_VAR and DELTA_MINUS_VAR variable attributes.
;                           FILLVAL is removed from dependent variable in 3D Spectrograms. - MRA
;       2013/11/25  -   DELTA_[PLUS/MINUS]_VAR can have a record variance of 'VARY'. - MRA
;       2014/01/09  -   Can now force a time-series data poduct to be displayed as an
;                           image if DEPEND_1 is present. Added the SUM_PAGES keyword. - MRA
;       2014/01/20  -   If fewer than two data points are read, no plot will be made. This
;                           is because trailing shallow dimensions are trimmed
;                           automatically by IDL and wind up causing errors. -  MRA
;       2014/04/01  -   Output an informational message if a plot is not made because
;                           no data is available. - MRA
;-
;*****************************************************************************************
;+
;   The purpose of this method is to create a time series plot object based on the data
;   returned from PLOT_CDF.
;
; :Params:
;       VNAME:              in, required, type=string
;                           Name of the variable whose metadata is to be stored.
;       PDATA:              in, required, type=pointer
;                           A pointer to the data associated with `VNAME`
;       PMETADATASTRUCT:    in, required, type=structure
;                           A structure containing data and metadata pertaining to
;                               `VARIABLE`. and its dependents.
;-
pro CDF_Plot::Append_MetaStruct, vname, pData, pMetaDataStruct
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

;---------------------------------------------------------------------
;Create a Data Structure \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    ;Get the metadata. Put name, data, and metadata into a structure.
    metadata = self -> GetMetaData(vname, pData, _EXTRA=extra)
    data_struct = create_struct('name', vname, 'data', pData, metadata)

;---------------------------------------------------------------------
;Pick the New Structure Tag \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

    tags = ['variable', 'depend_0', 'depend_1', 'depend_2', 'depend_3']
    meta_tags = tag_names(*pMetaDataStruct)
    void = isMember(meta_tags, tags, N_NONMEMBER=nNonMembers, NONMEMBER_INDS=iTags, /FOLD_CASE)
    
    ;Pick the first tag that is not present.
    thisTag = tags[iTags[0]]

;---------------------------------------------------------------------
;Append the Data Structure \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    *pMetaDataStruct = create_struct(*pMetaDataStruct, thisTag, temporary(data_struct))
end


;+
;   The purpose of this method is to create a time series plot object based on the data
;   returned from PLOT_CDF.
;
;   :Params:
;       PMETASTRUCT:            in, required, type=pointer
;                               A pointer to a structure containing data and metadata
;                                   pertaining to a chosen variable. This is the return
;                                   value of the CDF_Plot::Plot_Setup method. This is
;                                   destroyed upon exit.
;
;   :Returns:
;       OTIMESERIES:            An reference to a plot object that can be displayed on
;                                   its own (by calling the displayObj -> Draw method)
;                                   or in a MrWindow window. An invalid object reference
;                                   will be returned if an error occurs
;-
function CDF_Plot::Create_Time_Series, pMetaStruct, name
    compile_opt strictarr

    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        
        if ptr_valid(pStruct) then undefine, pStruct
        if obj_valid(oTimeSeries) then oTimeSeries -> Close
        if obj_valid(oLegend) then oLegend -> Close
        
        void = cgErrorMsg()
        return, obj_new()
    endif
    
    case n_elements(name) of
        0: onames = ['', 'Legend: '] + (*pMetaStruct).variable.name
        1: onames = ['', 'Legend: '] + names
        2: onames = name
        else: message, 'NAME: Incorrect number of arguments'
    endcase
            
;---------------------------------------------------------------------
;Axis Titles + Units /////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Make the x-axis title as 'X-Title (Units)'
    if n_elements(*(*pMetaStruct).depend_0.axtitle) eq 0 $
        then xtitle = '' $
        else xtitle = *(*pMetaStruct).depend_0.axtitle
        
    if n_elements(*(*pMetaStruct).depend_0.units) eq 0 $
        then xtitle += '' $
        else xtitle += ' (' + *(*pMetaStruct).depend_0.units + ')'

    ;Make the y-axis title as 'Y-Title !C (Units)' (!C = carriage return)
    if n_elements(*(*pMetaStruct).variable.axtitle) eq 0 $
        then ytitle = '' $
        else ytitle = *(*pMetaStruct).variable.axtitle
        
    if n_elements(*(*pMetaStruct).variable.units) eq 0 $
        then ytitle += '' $
        else ytitle += '!C(' + *(*pMetaStruct).variable.units + ')'
        
    yrange = *(*pMetaStruct).variable.axrange
    yrange = yrange + abs(yrange) * [-0.05, 0.05]

;---------------------------------------------------------------------
;Min and Max Values //////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;min_value
    case n_elements((*(*pMetaStruct).variable.min_value)) of
        0:    ;do nothing
        1:    min_value =  *(*pMetaStruct).variable.min_value
        else: min_value = (*(*pMetaStruct).variable.min_value)[0]
    endcase

    ;max_value
    case n_elements((*(*pMetaStruct).variable.max_value)) of
        0:    ;do nothing
        1:    max_value =  *(*pMetaStruct).variable.max_value
        else: max_value = (*(*pMetaStruct).variable.max_value)[0]
    endcase

;---------------------------------------------------------------------
;Convert from Epoch //////////////////////////////////////////////////
;---------------------------------------------------------------------
    
    if n_elements(*(*pMetaStruct).depend_0.data) ne 0 then begin
        *(*pMetaStruct).depend_0.data = epoch_to_ssm(*(*pMetaStruct).depend_0.data)
        *(*pMetaStruct).depend_0.axrange = epoch_to_ssm(*(*pMetaStruct).depend_0.axrange)
        xtickformat = ptr_new('time_labels')
    endif else xtickformamt = ptr_new(/ALLOCATE_HEAP)

;---------------------------------------------------------------------
;Legend //////////////////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;If a legend has not been made from the LABL_PTR_1, then use the values
    ;of the DEPEND_1 variable to create the legend.
    if ((*pMetaStruct).display_dim ne 0) and $
       (n_elements(*(*pMetaStruct).variable.legend) eq 0) and $
       (has_tag(*pMetaStruct, 'DEPEND_1') eq 1) $
    then begin
        legend_values = self.ocdf -> Read((*pMetaStruct).depend_1.name)
        legend = string(legend_values, FORMAT='(F0.3)') + ' ' + *(*pMetaStruct).depend_1.units
    endif else begin
        legend = *(*pMetaStruct).variable.legend
    endelse
;---------------------------------------------------------------------
;Create the Plot Object //////////////////////////////////////////////
;---------------------------------------------------------------------

    ;
    ; If VARIABLE data is a multidimensional time series, then we
    ; must take the 0-th index of MIN_VALUE and MAX_VALUE. However,
    ; if MIN_VALUE and MAX_VALUE are undefined for the variable, then
    ; this will cause an error because !Null[0] does not exist.
    ;
    oTimeSeries = MrPlot(*(*pMetaStruct).depend_0.data, $
                         *(*pMetaStruct).variable.data, $
                         DIMENSION=(*pMetaStruct).display_dim, $
                         MIN_VALUE=min_value, $
                         MAX_VALUE=max_value, $
                         NAME=onames[0], $
                         TITLE=*(*pMetaStruct).variable.title, $
                         XLOG=*(*pMetaStruct).depend_0.axlog, $
                         XRANGE=*(*pMetaStruct).depend_0.axrange, $
                         XTICKFORMAT=*xtickformat, $
                         XTITLE=xtitle, $
                         YLOG=*(*pMetaStruct).variable.axlog, $
                         YRANGE=yrange, $
                         YTITLE=ytitle, $
                         /CURRENT)

    ;Do not make a legend until I figure out what to put in it.
    if n_elements(legend) gt 0 then begin
        colors = oTimeSeries.color
    
        oLegend = MrLegend(TITLE=legend, $
                           TARGET=oTimeSeries, $
                           TCOLORS=colors, $
                           LOCATION=8, $
                           LENGTH=0.0, $
                           COLORS=colors, $
                           NAME=onames[1], $
                           /CURRENT)
    endif

    undefine, pMetaStruct
    return, oTimeSeries
end


;+
;   The purpose of this method is to create a time series plot object based on the data
;   returned from PLOT_CDF.
;
; :Params:
;       PSTRUCT:                in, required, type=structure
;                               A structure containing data and metadata pertaining to
;                                   a chosen variable. This is the return value of the
;                                   CDF_Plot::Plot_Setup method. This is destroyed upon
;                                   exit.
;       NAME:                   in, optional, type=string/strarr(2), default=[''\, 'CB: '] + variable_name
;                               Name of the image and colorbar.
;
; :Keywords:
;       SUM_PAGES:              in, optional, type=boolean, default=0
;                               If set, dimensions 3+ of the image data will be summed.
;
; :Returns:
;       oImage:                 An reference to an image object that can be displayed on
;                                   its own (by calling the oImage -> Draw method)
;                                   or in a MrWindow window.
;-
function CDF_Plot::Create_3D_Spectrogram, pStruct, name, $
SUM_PAGES=sum_pages
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        
        if ptr_valid(pStruct) then undefine, pStruct
        if obj_valid(oImage) then oImage -> Close
        if obj_valid(oColorbar) then oColorbar -> Close
        
        void = cgErrorMsg()
        return, obj_new()
    endif
    
    ;Defaults
    sum_pages = keyword_set(sum_pages)
    
    case n_elements(name) of
        0: onames = ['', 'Colorbar: '] + (*pStruct).variable.name
        1: onames = ['', 'Colorbar: '] + names
        2: onames = name
        else: message, 'NAME: Incorrect number of arguments'
    endcase
            
;---------------------------------------------------------------------
;Map between DEPEND_N and M-th Image Dimension ///////////////////////
;---------------------------------------------------------------------

    depend_order = self -> Sort_Depend((*pStruct).variable.name)

;---------------------------------------------------------------------
;Colorbar Dimension //////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Make the axis title as 'Title !C (Units)' (!C = carriage return)
    if n_elements(*(*pStruct).variable.axtitle) eq 0 $
        then cbtitle='' $
        else cbtitle = *(*pStruct).variable.axtitle

    if n_elements(*(*pStruct).variable.axtitle) eq 0 $
        then cbtitle += '' $
        else cbtitle += '!C(' + *(*pStruct).variable.axtitle + ')'
        
;---------------------------------------------------------------------
;Dependent Variable //////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Figure out which dependent variable is being displayed
    iDepend = where(depend_order eq (*pStruct).display_dim) + 1
    iDepend = iDepend[0]

    ;Copy pointers to data and metadata
    case iDepend of
        1: begin
            axtitle     = (*pStruct).depend_1.axtitle
            delta_minus = (*pStruct).depend_1.delta_minus_var
            delta_plus  = (*pStruct).depend_1.delta_plus_var
            dminus_fill = (*pStruct).depend_1.delta_minus_fillval
            dplus_fill  = (*pStruct).depend_1.delta_plus_fillval
            fillval     = (*pStruct).depend_1.fillval
            dep_data    = (*pStruct).depend_1.data
            units       = (*pStruct).depend_1.units
            ylog        = (*pStruct).depend_1.axlog
            yrange      = (*pStruct).depend_1.axrange
        endcase
        
        2: begin
            axtitle     = (*pStruct).depend_2.axtitle
            delta_minus = (*pStruct).depend_2.delta_minus_var
            delta_plus  = (*pStruct).depend_2.delta_plus_var
            dminus_fill = (*pStruct).depend_2.delta_minus_fillval
            dplus_fill  = (*pStruct).depend_2.delta_plus_fillval
            fillval     = (*pStruct).depend_2.fillval
            dep_data    = (*pStruct).depend_2.data
            units       = (*pStruct).depend_2.units
            ylog        = (*pStruct).depend_2.axlog
            yrange      = (*pStruct).depend_2.axrange
        endcase
        
        3: begin
            axtitle     = (*pStruct).depend_3.axtitle
            delta_minus = (*pStruct).depend_3.delta_minus_var
            delta_plus  = (*pStruct).depend_3.delta_plus_var
            dminus_fill = (*pStruct).depend_3.delta_minus_fillval
            dplus_fill  = (*pStruct).depend_3.delta_plus_fillval
            fillval     = (*pStruct).depend_3.fillval
            dep_data    = (*pStruct).depend_3.data
            units       = (*pStruct).depend_3.units
            ylog        = (*pStruct).depend_3.axlog
            yrange      = (*pStruct).depend_3.axrange
        endcase
        
        else: message, 'Depend number does not match.'
    endcase
    
    ;Make the Y-axis title as 'Y-Title (Units)'
    if n_elements(*axtitle) eq 0 $
        then ytitle='' $
        else ytitle = *axtitle

    if n_elements(*units) eq 0 $
        then ytitle += '' $
        else ytitle += ' (' + *units + ')'

    ;Make sure the dependent variable is monotonically increasing. Start by finding all
    ;non-fill values. If the first element is greater than the last element, then the
    ;values are monotically decreasing and need to be reversed.
    depvals = where((*dep_data)[*,0] ne *fillval, count)
    if count gt 0 then if (*dep_data)[depvals[0]] gt (*dep_data)[depvals[count-1]] then begin
        *dep_data = reverse(*dep_data, 1)
        if n_elements(*delta_minus) gt 0 then *delta_minus = reverse(*delta_minus, 1)
        if n_elements(*delta_plus)  gt 0 then *delta_plus  = reverse(*delta_plus,  1)
        *(*pStruct).variable.data = reverse(*(*pStruct).variable.data, depend_order[iDepend-1]+1)
    endif
    
    ;Replace the fill values
    *dep_data = replace_fillval(*dep_data, *fillval)
    
    if n_elements(*delta_plus) gt 0 and n_elements(*dplus_fill) gt 0 $
        then *delta_plus = replace_fillval(*delta_plus, *dplus_fill, REPLACE_VALUE=0)
    
    if n_elements(*delta_minus) gt 0 and n_elements(*dminus_fill) gt 0 $
        then *delta_minus = replace_fillval(*delta_minus, *dminus_fill, REPLACE_VALUE=0)

;---------------------------------------------------------------------
;Independent Variable ////////////////////////////////////////////////
;---------------------------------------------------------------------
        
    ;Make the x-axis title as 'X-Title (Units)'
    if n_elements(*(*pStruct).depend_0.axtitle) eq 0 $
        then xtitle='' $
        else xtitle = *(*pStruct).depend_0.axtitle

    if n_elements(*(*pStruct).depend_0.units) eq 0 $
        then xtitle += '' $
        else xtitle += ' (' + *(*pStruct).depend_0.units + ')'

    if n_elements(*(*pStruct).depend_0.data) gt 0 then begin
        indep_data = ptr_new(epoch_to_ssm(*(*pStruct).depend_0.data))
        xrange = ptr_new(epoch_to_ssm(*(*pStruct).depend_0.axrange))
        xtickformat = ptr_new('time_labels')
    endif else begin
        indep_data = (*pStruct).depend_0.data
        xrange = (*pStruct).depend_0.axrange
        xtickformat = ptr_new(/ALLOCATE_HEAP)
    endelse
    
    x_delta_minus = (*pStruct).depend_0.delta_minus_var
    x_delta_plus  = (*pStruct).depend_0.delta_plus_var
    
;---------------------------------------------------------------------
;Reorder Dimensions //////////////////////////////////////////////////
;---------------------------------------------------------------------
    ;
    ; Image data needs to have its dimensions ordered as [x,y,A,B,C,...]
    ; in order to be displayed properly. Here, "x" and "y" correspond to the
    ; independent and dependent variables, respectively. If dimensions
    ; [A,B,C,...] exist, a 2D [x,y] cut will be taken at index 0. The iDisplay
    ; property of MrImageObject controls this index and can be set or paged
    ; through with the wheel zoom functionality.
    ;

    ;Find how the DEPEND_N variables relate to the data dimensions
    ndims = size(*(*pStruct).variable.data, /N_DIMENSIONS)

    depend_order = depend_order[where(depend_order ne -1)]
    
    ;Order the dimensions [x, y, ...]. DEPEND_0 is not included in DEPEND_ORDER, but is
    ;always the x-dimension. DISPLAY_DIM is the y-dimension. We just have to figure out
    ;which dimensions are not those. 
    iDims = indgen(ndims)
    void = ismember(depend_order, iDims, NONMEMBER_INDS=iDep0)
    void = ismember([iDep0, (*pStruct).display_dim], iDims, N_NONMEMBERS=nDepN, NONMEMBER_INDS=iDepN)
    
    ;How to transpose the data to be in the proper order
    if nDepN gt 0 $
        then dim_order = [iDep0, (*pStruct).display_dim, iDepN] $
        else dim_order = [iDep0, (*pStruct).display_dim]
    
    ;Order the image
    if array_equal(iDims, dim_order) eq 0 $
        then image = transpose(*(*pStruct).variable.data, dim_order) $
        else image = (*pStruct).variable.data

;---------------------------------------------------------------------
;Manipulate the Image ////////////////////////////////////////////////
;---------------------------------------------------------------------
    
    ;Sum over the extra dimensions?
    if sum_pages then if nDepN gt 0 then begin
        while nDepN gt 0 do begin
            image = total(image, 3, /NAN)
            nDepN -= 1
        endwhile
    endif

    ;Take the log so that the data range is not too large
    image = MrLog(image)

;---------------------------------------------------------------------
;Create the Image ////////////////////////////////////////////////////
;---------------------------------------------------------------------

    range = [min(image, max=iMax, /NAN), iMax]
    
    if n_elements(*x_delta_minus) gt 0 && n_elements(*delta_minus) gt 0 && $
       n_elements(*x_delta_plus)  gt 0 && n_elements(*delta_plus)  gt 0 $
    then begin
        oImage = MrImage(image, $
                         *indep_data, *dep_data, $
                         *x_delta_minus, *delta_minus, *x_delta_plus, *delta_plus, $
                         /AXES, $
                         CTINDEX=13, $
                         NAME=onames[0], $
                         /NAN, $
                         MISSING_COLOR='white', $
                         RANGE=range, $
                         TITLE=*(*pStruct).variable.title, $
                         XLOG=*(*pStruct).depend_0.axlog, $
                         XRANGE=*xrange, $
                         XTICKFORMAT=*xtickformat, $
                         XTITLE=xtitle, $
                         YLOG=*ylog, $
                         YRANGE=*yrange, $
                         YTITLE=ytitle, $
                         /CURRENT)
    endif else begin
        oImage = MrImage(image, $
                         *indep_data, *dep_data, $
                         /AXES, $
                         CTINDEX=13, $
                         NAME=onames[0], $
                         /NAN, $
                         MISSING_COLOR='white', $
                         RANGE=range, $
                         TITLE=*(*pStruct).variable.title, $
                         XLOG=*(*pStruct).depend_0.axlog, $
                         XRANGE=*xrange, $
                         XTICKFORMAT=*xtickformat, $
                         XTITLE=xtitle, $
                         YLOG=*ylog, $
                         YRANGE=*yrange, $
                         YTITLE=ytitle, $
                         /CURRENT)
    endelse

    ;Put the colorbar in the same window as the image
    oColorbar = MrColorbar(TARGET=oImage, $
                           TITLE=cbtitle, $
                           NAME = onames[1], $
                           YLOG=*(*pStruct).variable.axlog, $
                           /CURRENT)

    ;Bind the color ranges
    oImage.window -> Bind, [oImage, oColorbar], /CAXIS

    undefine, pStruct
    return, oImage
end


;+
;   The purpose of this method is to destroy the object.
;-
pro CDF_Plot::destroy
    obj_destroy, self
end


;+
;   The purpose of this method is to plot data from a CDF object.
;
;   If `VARIABLE` is not provided
;
; :Params:
;       VARIABLE:               in, out, optional, type=string
;                               The name of the variable whose data is to be plotted. If
;                                   not provided, a dialog box will appear from which one
;                                   can be selected.
;
; :Keywords:
;       CURRENT:                in, optional, type=boolean, default=0
;                               If set, the graphics are added to the current MrWindow
;                                   graphics window.
;       DISPLAY_TYPE:           out, optional, type=string
;                               The type of plot to be made. Options are::
;                                   'TIME_SERIES'
;                                   '3D_SPECTROGRAM'
;       DISPLAY_DEP:            in, optional, type=string, default=''
;                               The dependent variable to be displayed. Choices are
;                                   'DEPEND_1', 'DEPEND_2', or 'DEPEND_3'.
;       DISPLAY_DIM:            out, optional, type=string
;                               If `DISPLAYOBJ` is not of type 'TIME_SERIES', then this
;                                   is the dimension of `DISPLAYOBJ` that should be
;                                   displayed.
;       FILENAME:               in, out, optional, type=string
;                               The CDF file from which to read data. If not provided, a
;                                   dialog box will appear from which a file can be selected.
;       FORCE_IMAGE:            in, optional, type=boolean, default=0
;                               If set, time-series data will be plotted as an image, not
;                                   as a line plot. This requires `VARIABLE` to have a
;                                   DEPEND_1 attribute and for its "Display Type" to be
;                                   "Time Series".
;       GROUP_LEADER:           in, optional, type=integer
;                               The group leader of the variable selection gui. Use only when
;                                   `VARIABLE` is undefined.
;       NAME:                   in, optional, type=string, default=`VARIABLE`
;                               A scalar string or a 2-element array of strings indicating
;                                   the name of the graphic[, annotation] created. By
;                                   default, the variable name is used. In the case of
;                                   annotations, it is '[Annotation type]: `VARIABLE`'.
;       SUM_PAGES:              in, optional, type=boolean, default=0
;                               If set, then images with more than two dimensions will
;                                   have their extra dimension summed. This option is
;                                   is ignored if `DISPLAY_TYPE` is not "3D_SPECTROGRAM".
;       _REF_EXTRA:             in, optional, type=structure
;                               Any keyword accepted by the CDF_Read::READ method.
;-
function CDF_Plot::Plot, variable, $
 CURRENT = current, $
 DISPLAY_TYPE = display_type, $
 DISPLAY_DEP = display_dep, $
 DISPLAY_DIM = display_dim, $
 FILENAME = filename, $
 FORCE_IMAGE = force_image, $
 GROUP_LEADER = group_leader, $
 NAME = name, $
 SUM_PAGES = sum_pages, $
_REF_EXTRA = extra
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        
        if ptr_valid(pStruct) then undefine, pStruct
        if obj_valid(displayObj) then obj_destroy, displayObj
        
        void = cgErrorMsg()
        return, obj_new()
    endif
    
    setDefaultValue, filename, ''
    setDefaultValue, current, 0, /BOOLEAN
    setDefaultValue, display_dep, ''
    setDefaultvalue, force_image, 0, /BOOLEAN
            
;---------------------------------------------------------------------
;Open File ///////////////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Open the CDF file?
    if n_elements(variable) eq 0 then begin
        oVS = obj_new('CDF_VarSelect', self.oCDF)
        if obj_valid(oVS) eq 0 then return, obj_new()

        oVS -> GetProperty, VARIABLE=variable, DIMENSION=dimension, DEPEND=display_dep
        oVS -> Destroy
        if n_elements(variable) eq 0 then return, obj_new()
    endif

;---------------------------------------------------------------------
;Force an Image Plot? ////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Changing from an line plot to an image?
    if force_image then begin
        ;Check the display type of the variable
        self.oCDF -> Get_ConfigVals, variable, DISPLAY_TYPE=display_type
        
        ;Can we force it to be an image?
        if strupcase(display_type) eq 'TIME_SERIES' then begin
            display_dep = 'DEPEND_1'
        endif else begin
            message, 'Data is not a time series. Setting FORCE_IMAGE=0.', /INFORMATIONAL
            force_image = 0
        endelse
    endif

;---------------------------------------------------------------------
;Collect Data ////////////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Create a structure for plotting.
    pStruct = self -> Gather_Data(variable, $
                                  DISPLAY_DEP=display_dep, $
                                  REPLACE_FILLVAL=replace_fillval, $
                                 _EXTRA=extra)
    if ptr_valid(pStruct) eq 0 then return, obj_new()
    
    ;Force an image?
    if force_image then (*pStruct).display_type = '3D_SPECTROGRAM'
    
    ;Return the display type and dimension
    display_type = (*pStruct).display_type
    display_dim = (*pStruct).display_dim

;---------------------------------------------------------------------
;Create Plot /////////////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Create a new window, if need be
    if current eq 0 then displayWin = MrWindow(XMARGIN=[10,14], REFRESH=0)

    ;Create an object.
    case strupcase((*pStruct).display_type) of
        'TIME_SERIES':    displayObj = self -> Create_Time_Series(pStruct, name)
        'SPECTROGRAM':    displayObj = self -> Create_3D_Spectrogram(pStruct, name, SUM_PAGES=sum_pages)
        '3D_SPECTROGRAM': displayObj = self -> Create_3D_Spectrogram(pStruct, name, SUM_PAGES=sum_pages)
        else: message, 'Display type "' + (*pStruct).display_type + '" not recognized.'
    endcase

    ;If CURRENT was not set, then draw the plot
    if current eq 0 then displayWin -> Refresh

    undefine, pStruct
    return, displayObj
end


;+
;   The purpose of this method is to determine how do display variable data and create
;   a structure of data and graphics keywords that can be used to display a waveform
;   or an image.
;
;   The return value is organized as::
;
;       display_struct.display_type
;                     .display_dim
;                     .dim_order
;                     .variable.name
;                              .data
;                              .[MetaData Fields]
;                     .depend_0.name
;                              .data
;                              .labels
;                              .[MetaData Fields]
;                     .depend_1.name
;                              .data
;                              .labels
;                              .[MetaData Fields]
;                     .depend_2.name
;                              .data
;                              .labels
;                              .[MetaData Fields]
;                     .depend_3.name
;                              .data
;                              .labels
;                              .[MetaData Fields]
;
;   :Params:
;       VARIABLE:               in, optional, type=string/int
;                               The variable name or number of the CDF variable whose
;                                   meta-data will be used to fill the graphics keywords
;                                   structure
;
;   :Keywords:
;       REPLACE_FILLVAL:        in, optional, type=Boolean, default=0
;                               Relplace the fill value with the NaNs.
;       ZVARIABLE:              in, optional, type=Boolean, default=0
;                               Indicate that `VARIABLE` is a z-variable, not an
;                                   r-variable.
;       _REF_EXTRA:             in, optional, type=structure
;                               Any keyword accepted by the READ method.
;
;   :Returns:
;       PMETASTRUCT:        A pointer to a structure containing data (tags: x, y) and
;                               IDL graphicskeywords that can used to display the waveform
;                               or image data. If an error occurs, an invalid pointer will
;                               be returned.
;
; :Uses:
;   Uses the following external programs::
;       replace_fillval.pro
;-
function CDF_Plot::Gather_Data, variable, $
DISPLAY_DEP = display_dep, $
GROUP_LEADER = group_leader, $
REPLACE_FILLVAL = replace_fillval, $
ZVARIABLE = zvariable, $
_REF_EXTRA = extra
    compile_opt strictarr
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        if n_elements(pMetaDataStruct) ne 0 then undefine, pMetaDataStruct
        void = cgErrorMsg()
        return, ptr_new()
    endif

;---------------------------------------------------------------------
;Get Data and Determine Plot Type \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

    ;If VARIABLE was given, make sure it is a variable name
    if n_elements(variable) ne 0 then $
        vname = self.oCDF -> Ensure_VarType(variable, IS_ZVAR=zvariable, /NAME)

;---------------------------------------------------------------------
;Get Data and Determine Plot Type \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    
    ;Read the data.
    data = self.oCDF -> Read(vname, depend_0, depend_1, depend_2, depend_3, $
                             DEPEND_NAMES=depend_names, GROUP_LEADER=group_leader, $
                             REC_COUNT=rec_count, _EXTRA=extra)

    ;RETURN -- if the data was not read.
    if rec_count le 1 then begin
        message, 'No data found. Returning.', /INFORMATIONAL
        return, ptr_new()
    endif

    ;Replace fillvalues
    if keyword_set(replace_fillval) then begin
        self.oCDF -> Get_ConfigVals, vname, FILLVAL=fillval
        data = replace_fillval(data, fillval)
    endif

    ;Determine how the variable is to be displayed.
    self.oCDF -> Get_ConfigVals, vname, DISPLAY_TYPE=display_type
    if n_elements(display_type) eq 0 then begin
        if n_elements(depend_3) ne 0 then begin
            display_type = '4D_SPECTROGRAM'
        endif else if n_elements(depend_2) ne 0 then begin
            display_type = '3D_SPECTROGRAM'
        endif else if n_elements(depend_1) ne 0 then begin
            display_type = '2D_SPECTROGRAM'
        endif else begin
            display_type = 'TIME_SERIES'
        endelse
    endif

    ;Organize the data so that its dimensions are [x, y, A, B, C, ...]
    self -> Organize_Dimensions, vname, data, depend_0, depend_1, depend_2, depend_3, $
                                 DISPLAY_DEP = display_dep, $
                                 IDEP1 = iDep1, $
                                 IDEP2 = iDep2, $
                                 IDEP3 = iDep3
    
;---------------------------------------------------------------------
;Metadata Structure \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    ;Create a structure containing all of the information necessary
    ;to display the variable information properly
    pMetaDataStruct = ptr_new(create_struct('display_type', display_type, $
                                            'display_dim', self.thisDim))

    ;Variable data
    self -> Append_MetaStruct, vname, ptr_new(data, /NO_COPY), pMetaDataStruct
    
    ;Depend_0
    if n_elements(depend_0) ne 0 $
        then self -> Append_MetaStruct, depend_names[0], ptr_new(depend_0, /NO_COPY), pMetaDataStruct
    
    ;Depend_1
    if n_elements(depend_1) ne 0 $
        then self -> Append_MetaStruct, depend_names[1], ptr_new(depend_1, /NO_COPY), pMetaDataStruct
    
    ;Depend_2
    if n_elements(depend_2) ne 0 $
        then self -> Append_MetaStruct, depend_names[2], ptr_new(depend_2, /NO_COPY), pMetaDataStruct
    
    ;Depend_3
    if n_elements(depend_3) ne 0 $
        then self -> Append_MetaStruct, depend_names[3], ptr_new(depend_3, /NO_COPY), pMetaDataStruct
    
    return, pMetaDataStruct
end


;+
;   The purpose of this method is to retrieve and organize time series-related meta-data
;   from a CDF variable into a graphics keywords structure.
;
; :Private:
;
; :Params:
;       VARIABLE:               in, required, type=string/int
;                               The variable name or number of the CDF variable whose
;                                   meta-data will be used to fill the graphics keywords
;                                   structure
;       PDATA:                  in, optional, type=pointer
;                               The data associated with `VARIABLE`. If present, the data
;                                   range will be determined
;
; :Keywords:
;       _EXTRA                  in, optional, type=any
;                               Any keyword accepted by CDF_Read::Read is also accepted
;                                   for keyword inheritance. This is only used if the
;                                   DELTA_*_VAR variable attributes have a record variance
;                                   of `VARY`.
;
; :Returns:
;       METADATA:               A structure filled metadata related to `VARIABLE`. Fields
;                                   indlude::
;                                       AXLOG               - log-scale the axis
;                                       AXTITLE             - axis title
;                                       AXRANGE             - data range
;                                       DELTA_MINUS_FILLVAL - Fill value for DELTA_MINUS_VAR
;                                       DELTA_MINUS_VAR     - Lower limit of the data values
;                                       DELTA_PLUS_FILLVAL  - Fill value for DELTA_PLUS_VAR
;                                       DELTA_PLUS_VAR      - Upper limit of the data values
;                                       FILLVAL             - value used to replace bad or missing data
;                                       MIN_VALUE           - valid maximum data value
;                                       MAX_VALUE           - valid minimum data value
;                                       LEGEND              - data lables
;                                       TITLE               - title of the graphic
;                                       UNITS               - data units
;-
function CDF_Plot::GetMetaData, variable, pData, $
_EXTRA=extra
    compile_opt strictarr
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, !Null
    endif
        
    ;Axis attributes
    self.oCDF -> get_configVals, variable, ZVARIABLE=zvariable, $
                                 DELTA_MINUS_VAR = delta_minus_var, $
                                 DELTA_PLUS_VAR = delta_plus_var, $
                                 FIELDNAM = fieldnam, $
                                 FILLVAL = fillval, $
                                 LABLAXIS = lablaxis, $
                                 LABL_PTR_1 = labl_ptr_1, $
                                 LABL_PTR_2 = labl_ptr_2, $
                                 LABL_PTR_3 = labl_ptr_3, $
                                 UNITS = units, $
                                 UNIT_PTR = unit_ptr, $
                                 VALIDMIN = validmin, $
                                 VALIDMAX = validmax, $
                                 SCALETYP = scaletyp, $
                                 SCAL_PTR = scal_ptr, $
                                 SCALEMIN = scalemin, $
                                 SCALEMAX = scalemax
    
    ;TITLE
    if n_elements(fieldnam) eq 0 $
        then title = ptr_new(variable) $
        else title = ptr_new(fieldnam)

    ;[XY]TITLE
    if n_elements(lablaxis) eq 0 then begin
        if n_elements(fieldnam) eq 0 $
            then axtitle = ptr_new(/ALLOCATE_HEAP) $
            else axtitle = ptr_new(fieldnam)
    endif else axtitle = ptr_new(lablaxis, /NO_COPY)
    
    ;UNITS
    if n_elements(units) eq 0 $
        then units = ptr_new(/ALLOCATE_HEAP) $
        else units = ptr_new(units, /NO_COPY)
            
    ;[XY]LOG
    if n_elements(scaletyp) eq 0 $
        then axlog = ptr_new(/ALLOCATE_HEAP) $
        else axlog = ptr_new(strcmp(strupcase(scaletyp), 'LOG'))
        
    ;FILLVAL
    if n_elements(fillval) eq 0 $
        then fillval = ptr_new(/ALLOCATE_HEAP) $
        else fillval = ptr_new(fillval, /NO_COPY)
        

    ;[XY]RANGE: Get the range of the data, not including the fill value
    if ptr_valid(pData) and n_elements(*pData) gt 0 then begin
        if n_elements(*fillval) gt 0 then begin
            isdata = where(*pData ne *fillval)
            min_data = min((*pData)[isdata], max=max_data)
            
        endif else begin
            min_data = min(*pData, max=max_data)
        endelse

        axrange = ptr_new([min_data, max_data])
    endif else axrange = ptr_new(/ALLOCATE_HEAP)


    ;LEGEND: labl_ptr_1 variables are not always correctly named. Make sure the variable
    ;        exists before getting its data.
    if n_elements(labl_ptr_1) ne 0 then begin
        if self.oCDF -> VarExists(labl_ptr_1) $
            then legend = ptr_new(self.oCDF -> Read(labl_ptr_1, /STRING)) $
            else legend = ptr_new(/ALLOCATE_HEAP)
    endif else legend = ptr_new(/ALLOCATE_HEAP)

    ;MIN_VALUE
    if n_elements(validmin) gt 0 $
        then min_value = ptr_new(validmin, /NO_COPY) $
        else min_value = ptr_new(/ALLOCATE_HEAP)
    
    ;MAX_VALUE
    if n_elements(validmax) gt 0 $
        then max_value = ptr_new(validmax, /NO_COPY) $
        else max_value = ptr_new(/ALLOCATE_HEAP)

    ;DELTA_MINUS_VAR -- value and fillvalue
    if n_elements(delta_minus_var) gt 0 then begin
        ;Sometimes this variable is the actual data.
        if size(delta_minus_var, /TNAME) ne 'STRING' then begin
            message, 'WARNING: Delta_Minus_Var is not a variable name.', /INFORMATIONAL
            delta_minus = ptr_new(delta_minus_var, /NO_COPY)
            delta_minus_fillval = ptr_new(/ALLOCATE_HEAP)

        ;It SHOULD be a variable name
        endif else begin
            ;Check for record variance
            self.oCDF -> Get_VarInfo, delta_minus_var, RECVAR=recvar
            if recvar eq 'VARY' $
                then delta_minus = self.oCDF -> Read(delta_minus_var, _EXTRA=extra) $
                else delta_minus = self.oCDF -> Read(delta_minus_var)

            delta_minus = ptr_new(delta_minus, /NO_COPY)
            self.oCDF -> Get_ConfigVals, delta_minus_var, FILLVAL=delta_fillval
            delta_minus_fillval = ptr_new(delta_fillval, /NO_COPY)
        endelse
    endif else begin
        delta_minus = ptr_new(/ALLOCATE_HEAP)
        delta_minus_fillval = ptr_new(/ALLOCATE_HEAP)
    endelse

    ;DELTA_PLUS_VAR -- value and fillvalue
    if n_elements(delta_plus_var) gt 0 then begin
        ;Sometimes this variable is the actual data.
        if size(delta_plus_var, /TNAME) ne 'STRING' then begin
            message, 'WARNING: Delta_Plus_Var is not a variable name.', /INFORMATIONAL
            delta_plus = ptr_new(delta_minus_var, /NO_COPY)
            delta_plus_fillval = ptr_new(/ALLOCATE_HEAP)
            
        ;It SHOULD be a variable name
        endif else begin
            ;Check for record variance.
            self.oCDF -> Get_VarInfo, delta_plus_var, RECVAR=recvar
            if recvar eq 'VARY' $
                then delta_plus = self.oCDF -> Read(delta_plus_var, _EXTRA=extra) $
                else delta_plus = self.oCDF -> Read(delta_plus_var)
            
            delta_plus = ptr_new(delta_plot, /NO_COPY)
            self.oCDF -> Get_ConfigVals, delta_plus_var, FILLVAL=delta_fillval
            delta_plus_fillval = ptr_new(delta_fillval, /NO_COPY)
        endelse
    endif else begin
        delta_plus = ptr_new(/ALLOCATE_HEAP)
        delta_plus_fillval = ptr_new(/ALLOCATE_HEAP)
    endelse

    ;Put everything into a structure
    metadata = {axlog: axlog, $
                axtitle: axtitle, $
                axrange: axrange, $
                delta_minus_fillval: delta_minus_fillval, $
                delta_minus_var: delta_minus, $
                delta_plus_fillval: delta_plus_fillval, $
                delta_plus_var: delta_plus, $
                fillval: fillval, $
                min_value: min_value, $
                max_value: max_value, $
                legend: legend, $
                title: title, $
                units: units}
                
    return, metadata
end


;+
;   The purpose of this program is to organize the data. Specifically::
;
;       1. If a subset of data was selected from the GUI lists, then extract those
;           subarrays from the data and depended variables.
;       2. Transpose the data so that it is ready for plotting. The leading dimensions of
;           data will become associated [x,y], so that image data can be passed directly
;           to, e.g., cgImage or the TV command.
;
; :Params:
;       VNAME:              in, required, type=string
;                           Name or number of the variable whose data is to be organized.
;       DEPEND_0:           out, optional, type=depends
;                           Data for the 0-th dependent variable
;       DEPEND_1:           out, optional, type=depends
;                           Data for the 1-st dependent variable
;       DEPEND_2:           out, optional, type=depends
;                           Data for the 2-nd dependent variable
;       DEPEND_3:           out, optional, type=depends
;                           Data for the 3-rd dependent variable
;
; :Keywords:
;       DISPLAY_DEP:        in, optional, type=string, default=DEPEND_0
;                           The dependent variable associated with the data dimension to
;                               be displayed.
;-
pro CDF_Plot::Organize_Dimensions, vname, data, depend_0, depend_1, depend_2, depend_3, $
DISPLAY_DEP = display_dep, $
IDEP1 = iDep1, $
IDEP2 = iDep2, $
IDEP3 = iDep3
	compile_opt strictarr
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

;---------------------------------------------------------------------
;DATA Dimension Sizes \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    
    data_dims = size(data, /DIMENSIONS)

;---------------------------------------------------------------------
;DEPEND_N Dimension Sizes \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    
    ;Get variable attribute names
    self.oCDF -> Get_ConfigVals, vname, $
                                 DEPEND_1=depend_1_vname, $
                                 DEPEND_2=depend_2_vname, $
                                 DEPEND_3=depend_3_vname, $
                                 LABL_PTR_1=labl_ptr_1_vname, $
                                 LABL_PTR_2=labl_ptr_2_vname, $
                                 LABL_PTR_3=labl_ptr_3_vname
    
    if n_elements(depend_1_vname) eq 0 then depend_1_vname = ''
    if n_elements(depend_2_vname) eq 0 then depend_2_vname = ''
    if n_elements(depend_3_vname) eq 0 then depend_3_vname = ''
    if n_elements(labl_ptr_1_vname) eq 0 then labl_ptr_1_vname = ''
    if n_elements(labl_ptr_2_vname) eq 0 then labl_ptr_2_vname = ''
    if n_elements(labl_ptr_3_vname) eq 0 then labl_ptr_3_vname = ''
    
    
    ;Depend N
    depend_n = [depend_1_vname, depend_2_vname, depend_3_vname]
    
    depend_dims = lonarr(4)
    if depend_1_vname eq '' then dim_1 = 0 else self.oCDF -> Get_VarInfo, depend_1_vname, DIM=dim_1
    if depend_2_vname eq '' then dim_2 = 0 else self.oCDF -> Get_VarInfo, depend_2_vname, DIM=dim_2
    if depend_3_vname eq '' then dim_3 = 0 else self.oCDF -> Get_VarInfo, depend_3_vname, DIM=dim_3
    
    
    ;Dimensions of each DEPEND_N
    depend_dims = [n_elements(depend_0), dim_1, dim_2, dim_3]

;---------------------------------------------------------------------
;Select Subarrays of DATA and DEPEND_N \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    
    ;Get the label pointers
    label_ptrs = [labl_ptr_1_vname, labl_ptr_2_vname, labl_ptr_3_vname]

    ;Step through each LABEL
    label_names = strarr(3)
    label_dims = intarr(3)
    for iLabel = 0, 2 do begin
        
    ;---------------------------------------------------------------------
    ;Label Variable Names \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    ;---------------------------------------------------------------------
        
        ;Get the label's name
        if self.oCDF -> varHasAtt(vname, label_ptrs[iLabel], ATT_VALUE=att_value) $
            then label_names[iLabel] = att_value
    
        ;If this label does not exist, then there is nothing to trim
        if label_names[iLabel] eq '' then continue
        
    ;---------------------------------------------------------------------
    ;Subarray Indices \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    ;---------------------------------------------------------------------
        
        ;Get the DIM number for the label.
        self.oCDF -> get_varinfo, label_names[iLabel], DIM=dim
        label_dims[iLabel] = dim

        ;Indices of the subarray to extract. Continue if no subarray is desired.
        if iLabel eq 0 then if n_elements(iDep1) eq 0 then continue else iSelect = iDep1
        if iLabel eq 1 then if n_elements(iDep2) eq 0 then continue else iSelect = iDep2
        if iLabel eq 2 then if n_elements(iDep3) eq 0 then continue else iSelect = iDep3
        
    ;---------------------------------------------------------------------
    ;Select Subarray of DEPEND_N \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    ;---------------------------------------------------------------------

        ;Which DEPEND_N does LABEL_N correspond to?
        iDep = where(depend_dims eq dim)
        case iDep of
            0: depend_0 = depend_0[iSelect]
            1: depend_1 = depend_1[iSelect]
            2: depend_2 = depend_2[iSelect]
            3: depend_3 = depend_3[iSelect]
            else: ;Do nothing -- A "time_series" variable can have 2 dimensions without
                  ;              having DEPEND_N variables.
        endcase
        
    ;---------------------------------------------------------------------
    ;Select Subarrays of DATA \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    ;---------------------------------------------------------------------

        ;CDF variables have at most 4 dimensions, one for each DEPEND_N.
        ;If DATA has fewer than 4 dimensions, IDL will ignore the trailing
        ;asterisks.
        
        ;Which DATA dimension does LABEL_N correspond to?
        iData = where(data_dims eq dim)
        case iData of
            0: data = data[iSelect, *, *, *]
            1: data = data[*, iSelect, *, *]
            2: data = data[*, *, iSelect, *]
            3: data = data[*, *, *, iSelect]
        endcase
    endfor

;---------------------------------------------------------------------
;Which Dimension is to be Displayed? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

    ;Extract the DEPEND number.
    pos = stregex(display_dep, 'DEPEND_([0-4])', LEN=len, /SUBEXP)
    iDep = strmid(display_dep, pos[1], len[1])
    if iDep eq '' then iDep = 0 else iDep = fix(iDep)

    ;The dimension to be displayed.
    n_dims = size(data, /N_DIMENSIONS)
    case n_dims of
        ;Choose the only dimension available
        1: self.thisDim = 0
        
        ;Pick either the time dimension or the desired dimension
        2: begin
            if iDep eq 0 then begin
                iDim = where(data_dims eq depend_dims[0]) + 1
                self.thisDim = iDim[0]
            endif else self.thisDim = where(data_dims eq depend_dims[iDep])
        endcase
        
        ;Pick either the 1st non-time dimension or the desired dimension
        else: begin
            if iDep eq 0 then begin
                iDim = where(data_dims ne depend_dims[0])
                self.thisDim = iDim[0]
            endif else self.thisDim = where(data_dims eq depend_dims[iDep])
        endcase
    endcase
end


;+
;   The purpose of this method is to create a map between the DEPEND_# variables and
;   the dimensionas of the variable on which it depends. CDF files are often disorganized
;   and, e.g., DEPEND_1 does not always correspond to the 1st dimension of VNAME.
;
;   Note that this method does not try to identify DEPEND_0 because it is always
;   associated with the scalar quantity, MAXREC, not the array of DIMs.;
; :Params:
;       VNAME:          in, required, type=string
;                       The variable name whose DEPEND_# variabales are to be organized.
;
; :Returns:
;       DEPEND_ORDER:   out, required, type=intarr
;                       A map between the size of the DEPEND_N variables and the dimensions
;                       of the data associated with `VNAME`. As an example, say
;                       dimension sizes are as follows::
;                           DATA_DIMS    -  [31,16,75]
;                           DEPEND_0     -  [75]
;                           DEPEND_1     -  [16]
;                           DEPEND_2     -  [31]
;
;                       The result would then be::
;                           DEPEND_ORDER -  [1,0,-1]
;
;                       Now, because DEPEND_0 is not included in the result, index 0
;                       corresponds to DEPEND_1. Thus, the value of DEPEND_ORDER[0] is 1
;                       because the size of DEPEND_1 corresponds to DATA_DIMS[1]
;
;                       As an inverted example, say that you want to know which depend
;                       variable corresponds to the dimension size of 16 for DATA_DIMS.
;                       Since the dimension index is 1, you would need to figure out where
;                       in DEPEND_ORDER you can find the value of 1.
;
;                           iDepend = where(depend_order eq 1)
;
;                       The answer is iDepend = 0, which corresponds to DEPEND_1.
;-
function CDF_Plot::Sort_Depend, vname
    compile_opt strictarr
    
    ;catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, [-1, -1, -1]
    endif

    ;There are 4 possible DEPEND_# variables
    depend_order = intarr(3)

;---------------------------------------------------------------------
;Fill the Labels /////////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Get the Record and dimension size of the variable
    self.oCDF -> get_varinfo, vname, MAXREC=maxrec_var, DIM=dim_var
    
    ;Depend_N
    ;Get variable attribute names
    self.oCDF -> Get_ConfigVals, vname, $
                                 DEPEND_1=vname_dep1, $
                                 DEPEND_2=vname_dep2, $
                                 DEPEND_3=vname_dep3
    
    if n_elements(vname_dep1) eq 0 then vname_dep1 = ''
    if n_elements(vname_dep2) eq 0 then vname_dep2 = ''
    if n_elements(vname_dep3) eq 0 then vname_dep3 = ''
    
    depend_n = [vname_dep1, vname_dep2, vname_dep3]

    ;Step through each label
    for iDepend = 0, 2 do begin

        ;If the DEPEND_# variable exists
        if depend_n[iDepend] ne '' then begin
            ;Get its DIM size
            self.oCDF -> get_varinfo, depend_n[iDepend], DIM=dim
            
            ;Match the variable dimension to the DEPEND_# dimension and set the label
            iDim = where(dim_var eq dim[0])
            
            ;Store the order of the dimensions
            depend_order[iDepend] = iDim

        ;Otherwise, clear the label
        endif else begin
            depend_order[iDepend] = -1
        endelse
    endfor
    
    return, depend_order
end


;+
;   The purpose of this method is to create a map between the LABEL_# variables and
;   the dimensionas of the variable on which it depends. CDF files are often disorganized
;   and, e.g., LABEL_1 does not always correspond to the 1st dimension of VNAME.
;
; :Params:
;       VNAME:          in, required, type=string
;                       The variable name whose LABEL_# variabales are to be organized.
;-
function CDF_Plot::Sort_Labels, vname
    compile_opt strictarr
    
    ;catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, [-1, -1, -1]
    endif
    
    label_order = intarr(3)

;---------------------------------------------------------------------
;Order the Labels ////////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Get variable attribute names
    self.oCDF -> Get_ConfigVals, vname, $
                                 LABL_PTR_1=labl_ptr_1, $
                                 LABL_PTR_2=labl_ptr_2, $
                                 LABL_PTR_3=labl_ptr_3
    
    if n_elements(labl_ptr_1) eq 0 then labl_ptr_1 = ''
    if n_elements(labl_ptr_2) eq 0 then labl_ptr_2 = ''
    if n_elements(labl_ptr_3) eq 0 then labl_ptr_3 = ''
                                 
    ;Make the label pointers easier to get to
    label_pointers = [labl_ptr_1, labl_ptr_2, labl_ptr_3]
    
    ;Get the MAXREC and DIM sizes of VNAME
    self -> get_varinfo, vname, MAXREC=maxrec_var, DIM=dim_var

    ;Step through all of the labels
    for iLabl = 0, 2 do begin
        tf_label = self -> varHasAtt(vname, label_pointers[iLabl])

        ;If the label exists...
        if tf_label then begin
            ;Get the label's name, MAXREC, and DIMs
            label_name = self -> get_varatt(vname, label_pointers[iLabl])
            self -> get_varinfo, label_name, MAXREC=maxrec, DIM=dim
        
            ;Figure out which list to put the labels in (ordered by variable dimension)
            iDim = where(dim_var eq dim[0])

            ;Get the data labels and put them in the list.
            label_order[iLabl] = iDim

        ;Otherwise, mpty unused lists.
        endif else begin
            label_order[iLabl] = -1
        endelse
    endfor
    
    return, label_order
end


;+
;   The purpose of this method is to clean up after the object is destroyed. If a CDF_Plot
;   GUI is open, it will also be destroyed.
;-
pro CDF_Plot::Cleanup
	compile_opt strictarr
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

    ;Destroy object references
    if obj_valid(self.oCDF) then obj_destroy, self.oCDF
end


;+
;   The purpose of this method is to initialize the CDF_Read object reference.
;
; :Params:
;       thisCDF:        in, optional, type=string/object
;                       Either the file name of the CDF data file from which data is to
;                           be opened and plotted, or a valid CDF_Read object reference.
;                           If not provided, a file selection dialog box will appear.
;
; :Keywords:
;       GROUP_LEADER:   in, optional, type=integer
;                       The group leader of the file selection gui. Use only when
;                           `FILENAME` undefined.
;       _REF_EXTRA:     in, optional, type=any
;                       All keyword accepted by the SET_CONFIG method are also excepted
;                           for keyword inheritance.
;
; :Returns:
;       object reference
;-
function CDF_Plot::Init, thisCDF, $
GROUP_LEADER = group_leader, $
_REF_EXTRA = extra
	compile_opt strictarr
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, 0
    endif
    
	szCDF = size(thisCDF, /TYPE)

;---------------------------------------------------------------------
;OBJECT REFERENCE ////////////////////////////////////////////////////
;---------------------------------------------------------------------
	if szCDF eq 11 then begin
	    ;Is the object valid?
	    if obj_valid(thisCDF) eq 0 $
	        then message, 'thisCDF is not a valid object reference.' $
	        
	        ;Is it a CDF_Read object?
	        else if isa(thisCDF, 'CDF_Read') $
	            then self.oCDF = thisCDF $
	            else message, 'thisCDF must be a CDF_Read object.'
        
;---------------------------------------------------------------------
;FILENAME ////////////////////////////////////////////////////////////
;---------------------------------------------------------------------
	endif else if szCDF eq 7 then begin
	    oCDF = obj_new('CDF_Read', thisCDF, _EXTRA=extra)
	    if obj_valid(oCDF) $
	        then self.oCDF = oCDF $
	        else return, 0
        
;---------------------------------------------------------------------
;SELECT FILE /////////////////////////////////////////////////////////
;---------------------------------------------------------------------
	endif else begin
	
	    oCDF = obj_new('CDF_Read', _EXTRA=extra)
	    if obj_valid(oCDF) $
	        then self.oCDF = oCDF $
	        else return, 0 
	endelse
	
	return, 1
end


;+
;   Define the CDF_Plot class
;
; :Params:
;       CLASS:          out, optional, type=structure
;                       The class definition structure.
;
; :Fields:
;       OCDF:               An object reference to a CDF_Read object
;       THISDEPEND:         Dependent variable associated with the chosen leaf
;       THISDIM:            Dimension to be displayed
;-
pro CDF_Plot__define, class
	compile_opt strictarr
	
	class = { cdf_plot, $
	          oCDF: obj_new(), $            ;An object reference to a CDF_Read object
	          thisDepend: '', $             ;Dependent variable associated with the chosen leaf
	          thisDim: 0 $                  ;Dimension to be displayed
	        }
end